﻿using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using UnityEngine;
using System;
using System.Reflection;
using System.Linq;
using Utility.DataSystem.Attributes;
using System.Text;

namespace Utility.DataSystem
{
	public static class FieldCenter
	{
		public class DebugMessage : Text.TextBuilder
		{
			public DebugMessage ()
			{
				Indenter = "    ";
			}

			#region general
			public void StartContext (IDataFieldContext context)
			{
				AppendLine ($"Context: <b>{context.Name}</b>");
				IndentLevel++;
			}

			public void EndContext (DataValue value)
			{
				AppendLine ($"Value: {value}");
				IndentLevel--;
			}
			public void EndContext ()
			{
				IndentLevel--;
			}
			#endregion
			#region field
			public void StartQuery (FieldQuery query, IDataFieldContext context)
			{
				if (query.Setting.IsResult)
				{
					AppendLine ($"Query: <b>[{query.Setting.ResultKey}]</b>");
				}
				else
				{
					AppendLine ($"<b>[{query.Setting.ResultKey}][{query.Setting.ComponentType}]</b>");
				}
				AppendLine ($"From: {context.Name}");

				IndentLevel++;
			}

			public void EndQuery (FieldQuery query, DataValue value)
			{
				IndentLevel--;
				AppendLine ($"Value: {value}");
				if (query.Setting.IsResult)
				{
					if (IndentLevel == 0)
					{
						Debug.Log (Flush ());
					}
				}
			}

			public void InitValue (DataValue value)
			{
				if (value.HasValue)
				{
					AppendLine ($"InitValue: {value}");
				}
			}

			public void StartSource (IFieldSource source)
			{
				AppendLine ($"Source: {source.Name}");
				IndentLevel++;
			}

			public void EndSource (IFieldSource source, DataValue value, bool discard = false)
			{
				if (discard)
				{
					AppendLine ("Discard");
				}
				else
				{
					AppendLine ($"Value: {value}");
				}
				IndentLevel--;
			}

			public void FromCache ()
			{
				AppendLine ("From Cache");
			}
			#endregion
			#region event
			public void StartEvent (FieldEvent e, IDataFieldContext context)
			{
				AppendLine ($"Event: <b>{e.Setting.Key }</b>");
				AppendLine ($"From: {context.Name}");
				IndentLevel++;
			}

			public void EndEvent (FieldEvent e, IDataFieldContext context)
			{
				IndentLevel--;
				AppendLine ($"End");
				if (IndentLevel == 0)
				{
					Debug.Log (Flush ());
				}
			}

			public void StartListener (IFieldEventListener listener)
			{
				AppendLine ($"Listener: {listener.Name}");
				IndentLevel++;
			}

			public void EndListener (IFieldEventListener listener, bool discard = false)
			{
				if (discard)
				{
					AppendLine ("Discard");
				}
				IndentLevel--;
			}
			#endregion
			#region action
			public void StartAction (FieldAction a, IDataFieldContext context)
			{
				AppendLine ($"Action: <b>{a.Setting.Key}</b>");
				AppendLine ($"From: {context.Name}");
				IndentLevel++;
			}

			public void EndAction (FieldAction a, IDataFieldContext context)
			{
				IndentLevel--;
				AppendLine ($"End");
				if (IndentLevel == 0)
				{
					Debug.Log (Flush ());
				}
			}

			public void StartPerformer (IFieldActionPerformer performer)
			{
				AppendLine ($"Performer: {performer.Name}");
				IndentLevel++;
			}

			public void EndPerformer (IFieldActionPerformer performer, bool discard = false)
			{
				if (discard)
				{
					AppendLine ("Discard");
				}
				IndentLevel--;
			}
			#endregion
			#region effector
			public void EffectorVariable (string variable, DataValue value)
			{
				AppendLine ($"Var {variable}: {value}");
			}

			public void EffectorCondition (int index, bool pass)
			{
				AppendLine ($"Condition {index}: {pass}");
			}
			#endregion
		}

		static DebugMessage _debugMessage = new DebugMessage ();
		public static DebugMessage GetDebugMsg (FieldQuery query)
		{
			return query.Setting.DebugMode == 0 ? null : _debugMessage;
		}
		public static DebugMessage GetDebugMsg (FieldEvent e)
		{
			return e.Setting.DebugMode == 0 ? null : _debugMessage;
		}
		public static DebugMessage GetDebugMsg (FieldAction a)
		{
			return a.Setting.DebugMode == 0 ? null : _debugMessage;
		}

		static Dictionary<string, DataFieldSetting> fieldDict;
		static Dictionary<string, FieldEventSetting> eventDict;
		static Dictionary<string, FieldActionSetting> actionDict;
		public static ReadOnlyDictionary<string, DataFieldSetting> DataFields { get; private set; }
		public static ReadOnlyDictionary<string, FieldEventSetting> Events { get; private set; }
		public static ReadOnlyDictionary<string, FieldActionSetting> Actions { get; private set; }


		static FieldCenter ()
		{
			LoadFieldSettings ();
		}

		public static void LoadFieldSettings ()
		{
			DataFields = new ReadOnlyDictionary<string, DataFieldSetting> (fieldDict = new Dictionary<string, DataFieldSetting> ());
			Events = new ReadOnlyDictionary<string, FieldEventSetting> (eventDict = new Dictionary<string, FieldEventSetting> ());
			Actions = new ReadOnlyDictionary<string, FieldActionSetting> (actionDict = new Dictionary<string, FieldActionSetting> ());

			//fields
			var fieldEnumTypes = from assembly in AppDomain.CurrentDomain.GetAssemblies ()
								 from type in assembly.GetTypes ()
								 where type.IsEnum
								 let att = type.GetCustomAttribute<FieldEnumAttribute> ()
								 where att != null
								 select type;
			foreach (var type in fieldEnumTypes)
			{
				AddFieldEnumType (type);
			}

			//events
			var eventEnumTypes = from assembly in AppDomain.CurrentDomain.GetAssemblies ()
								 from type in assembly.GetTypes ()
								 where type.IsEnum
								 let att = type.GetCustomAttribute<EventEnumAttribute> ()
								 where att != null
								 select type;
			foreach (var type in eventEnumTypes)
			{
				AddEventEnumType (type);
			}
			//actions
			var actionEnumTypes = from assembly in AppDomain.CurrentDomain.GetAssemblies ()
								  from type in assembly.GetTypes ()
								  where type.IsEnum
								  let att = type.GetCustomAttribute<ActionEnumAttribute> ()
								  where att != null
								  select type;
			foreach (var type in actionEnumTypes)
			{
				AddActionEnumType (type);
			}
		}

		#region field
		public static DataFieldSettingObject DefineField (DataFieldSettingObject setting)
		{
			if (fieldDict.ContainsKey (setting.key))
			{
				Debug.LogWarning ($"[FieldCenter]Repeated field name: {setting.key}");
				return null;
			}

			fieldDict.Add (setting.key, setting.GetSetting (true));
			return setting;
		}

		public static DataFieldSetting GetField (string key)
		{
			fieldDict.TryGetValue (key ?? "", out var field);
			return field;
		}

		public static DataFieldSetting GetField (string name, FieldComponents componentType)
		{
			return GetField (componentType.GetKey (name));
		}

		public static DataFieldSetting GetField (Enum field, FieldComponents componentType = FieldComponents.Result)
		{
			return GetField (field.ToString (), componentType);
		}

		public static bool ContainsField (string key)
		{
			return fieldDict.ContainsKey (key ?? "");
		}
		#endregion
		#region event
		public static FieldEventSettingObject DefineEvent (FieldEventSettingObject setting)
		{
			if (eventDict.ContainsKey (setting.key))
			{
				Debug.LogWarning ($"[FieldCenter]Repeated event name: {setting.key}");
				return null;
			}

			eventDict.Add (setting.key, setting.GetSetting (true));
			return setting;
		}

		public static FieldEventSetting GetEvent (string key)
		{
			eventDict.TryGetValue (key ?? "", out var e);
			return e;
		}

		public static FieldEventSetting GetEvent (Enum field)
		{
			return GetEvent (field.ToString ());
		}

		public static bool ContainsEvent (string key)
		{
			return eventDict.ContainsKey (key ?? "");
		}
		#endregion
		#region action
		public static FieldActionSettingObject DefineAction (FieldActionSettingObject setting)
		{
			if (actionDict.ContainsKey (setting.key))
			{
				Debug.LogWarning ($"[FieldCenter]Repeated action name: {setting.key}");
				return null;
			}

			actionDict.Add (setting.key, setting.GetSetting (true));
			return setting;
		}

		public static FieldActionSetting GetAction (string key)
		{
			actionDict.TryGetValue (key ?? "", out var action);
			return action;
		}

		public static FieldActionSetting GetAction (Enum field)
		{
			return GetAction (field.ToString ());
		}

		public static bool ContainsAction (string key)
		{
			return actionDict.ContainsKey (key ?? "");
		}
		#endregion
		#region parse field enum
		struct SharedSettings_Field
		{
			public string prefix;
			public int contextFlags;
			public FieldComposition composition;
			public int debugMode;
			public bool editable;
			public bool contextLocal;
			public bool allowCache;
		}

		static void AddFieldEnumType (Type enumType)
		{
			var shared = new SharedSettings_Field ()
			{
				prefix = enumType.GetCustomAttribute<FieldEnumAttribute> ()?.prefix ?? "",
				contextFlags = enumType.GetCustomAttribute<ContextFlagsAttribute> ()?.Flags ?? 0,
				composition = enumType.GetCustomAttribute<CompositionAttribute> ()?.ComponentFlags ?? FieldComposition.Simple,
				debugMode = enumType.GetCustomAttribute<DebugAttribute> ()?.DebugMode ?? 0,
				editable = enumType.GetCustomAttribute<EditableAttribute> ()?.Editable ?? true,
				contextLocal = enumType.GetCustomAttribute<IsContextLocalAttribute> ()?.IsContextLocal ?? false,
				allowCache = enumType.GetCustomAttribute<AllowCacheAttribute> ()?.AllowCache ?? true,
			};

			var names = Enum.GetNames (enumType);
			foreach (var name in names)
			{
				var member = enumType.GetField (name);
				AddFieldEnumField (member, shared);
			}
		}

		//add enum field
		static void AddFieldEnumField (FieldInfo enumField, SharedSettings_Field shared)
		{
			try
			{
				var fieldAttr = enumField.GetCustomAttribute<FieldAttribute> ();
				var ContextFlagsAttr = enumField.GetCustomAttribute<ContextFlagsAttribute> ();
				var editableAttr = enumField.GetCustomAttribute<EditableAttribute> ();
				var componetAttr = enumField.GetCustomAttribute<CompositionAttribute> ();
				var debugAttr = enumField.GetCustomAttribute<DebugAttribute> ();
				var contextLocalAttr = enumField.GetCustomAttribute<IsContextLocalAttribute> ();
				var allowCacheAttr = enumField.GetCustomAttribute<AllowCacheAttribute> ();
				var paramAttrLookup = enumField.GetCustomAttributes<ParamAttribute> ().ToLookup (att => att.Index);

				if (fieldAttr == null)
				{
					return;
				}

				//define main field
				var setting = new DataFieldSettingObject ();
				setting.SetKey (enumField.Name);
				setting.type = fieldAttr.Type;

				setting.contextFlags = ContextFlagsAttr?.Flags ?? shared.contextFlags;
				setting.editable = editableAttr?.Editable ?? shared.editable;
				setting.isContextLocal = contextLocalAttr?.IsContextLocal ?? shared.contextLocal;

				setting.SetShowName (shared.prefix + fieldAttr.ShowName, FieldComponents.Result);

				setting.initValue = DataValue.Parse (fieldAttr.InitValue);

				setting.combiner = DataValueCombiners.GetCombiner (fieldAttr.CombineType);
				setting.SetMinMaxValidator (fieldAttr.Min, fieldAttr.Max);

				var composition = componetAttr?.ComponentFlags ?? shared.composition;
				setting.composition = composition;

				setting.debugMode = debugAttr?.DebugMode ?? shared.debugMode;
				setting.allowCache = allowCacheAttr?.AllowCache ?? shared.allowCache;

				setting.paramSettings = (from i in Enumerable.Range (0, fieldAttr.ParamCount)
										 let paramAttr = paramAttrLookup[i].FirstOrDefault ()
										 select new FieldParamSetting (paramAttr?.ShowName, paramAttr?.Type ?? typeof (double))).ToList ();

				//define components
				foreach (var type in composition.GetComponents ())
				{
					AddFieldComponent (setting, type);
				}

				setting.editable = false;//result is always not editable
				setting.SetCompositionValueGetter (composition);
				DefineField (setting);
			}
			catch (Exception e)
			{
				throw new Exception ($"\nCatch exception while parse {enumField}", e);
			}
		}

		//add field component
		static void AddFieldComponent (DataFieldSettingObject mainSetting, FieldComponents componentType)
		{
			DataFieldSettingObject setting = new DataFieldSettingObject ();
			setting.SetKey (mainSetting.key, componentType);
			setting.composition = mainSetting.composition;
			setting.type = componentType.GetFieldType () ?? mainSetting.type;

			setting.contextFlags = mainSetting.contextFlags;
			setting.editable = mainSetting.editable;
			setting.isContextLocal = mainSetting.isContextLocal;

			setting.SetShowName (mainSetting.showName, componentType);

			if (componentType == FieldComponents.Base)
			{
				setting.initValue = mainSetting.initValue;
			}

			setting.combiner = componentType.GetCombiner () ?? mainSetting.combiner;
			setting.SetMinMaxValidator (componentType.GetMin (), componentType.GetMax ());

			setting.debugMode = mainSetting.debugMode;
			setting.allowCache = mainSetting.allowCache;

			setting.paramSettings = mainSetting.paramSettings;

			DefineField (setting);
		}
		#endregion
		#region parse event enum
		struct SharedSettings_Event
		{
			public int contextFlags;
			public int allowedContextFlags;
			public string prefix;
			public int debugMode;
			public bool contextLocal;
		}

		static void AddEventEnumType (Type enumType)
		{
			var shared = new SharedSettings_Event ()
			{
				contextFlags = enumType.GetCustomAttribute<ContextFlagsAttribute> ()?.Flags ?? 0,
				allowedContextFlags = enumType.GetCustomAttribute<AllowedContextFlagsAttribute> ()?.Flags ?? ~0,
				prefix = enumType.GetCustomAttribute<EventEnumAttribute> ()?.prefix ?? "",
				debugMode = enumType.GetCustomAttribute<DebugAttribute> ()?.DebugMode ?? 0,
				contextLocal = enumType.GetCustomAttribute<IsContextLocalAttribute> ()?.IsContextLocal ?? false,
			};

			var names = Enum.GetNames (enumType);
			foreach (var name in names)
			{
				var member = enumType.GetField (name);
				AddEventEnumField (member, shared);
			}
		}

		static void AddEventEnumField (FieldInfo enumField, SharedSettings_Event shared)
		{
			var eventAttr = enumField.GetCustomAttribute<EventAttribute> ();

			if (eventAttr == null)
			{
				return;
			}

			var debugAttr = enumField.GetCustomAttribute<DebugAttribute> ();
			var flagsAttr = enumField.GetCustomAttribute<ContextFlagsAttribute> ();
			var allowedFlagsAttr = enumField.GetCustomAttribute<AllowedContextFlagsAttribute> ();
			var contextLocalAttr = enumField.GetCustomAttribute<IsContextLocalAttribute> ();

			//define 
			var setting = new FieldEventSettingObject ();
			setting.key = enumField.Name;
			setting.contextFlags = flagsAttr?.Flags ?? shared.contextFlags;
			setting.allowedContextFlags = allowedFlagsAttr?.Flags ?? shared.allowedContextFlags;
			setting.showName = shared.prefix + eventAttr.ShowName;
			setting.isContextLocal = contextLocalAttr?.IsContextLocal ?? shared.contextLocal;
			setting.debugMode = debugAttr?.DebugMode ?? shared.debugMode;

			DefineEvent (setting);
		}
		#endregion
		#region parse action enum
		struct SharedSettings_Action
		{
			public int flags;
			public string prefix;
			public int debugMode;
			public bool contextLocal;
		}

		static void AddActionEnumType (Type enumType)
		{
			var shared = new SharedSettings_Action ()
			{
				flags = enumType.GetCustomAttribute<ContextFlagsAttribute> ()?.Flags ?? 0,
				prefix = enumType.GetCustomAttribute<ActionEnumAttribute> ()?.prefix ?? "",
				debugMode = enumType.GetCustomAttribute<DebugAttribute> ()?.DebugMode ?? 0,
				contextLocal = enumType.GetCustomAttribute<IsContextLocalAttribute> ()?.IsContextLocal ?? false,
			};

			var names = Enum.GetNames (enumType);
			foreach (var name in names)
			{
				var member = enumType.GetField (name);
				AddActionEnumField (member, shared);
			}
		}

		static void AddActionEnumField (FieldInfo enumField, SharedSettings_Action shared)
		{
			var actionAttr = enumField.GetCustomAttribute<ActionAttribute> ();

			if (actionAttr == null)
			{
				return;
			}

			var flagsAttr = enumField.GetCustomAttribute<ContextFlagsAttribute> ();
			var paramAttrLookup = enumField.GetCustomAttributes<ParamAttribute> ().ToLookup (att => att.Index);
			var debugAttr = enumField.GetCustomAttribute<DebugAttribute> ();
			var contextLocalAttr = enumField.GetCustomAttribute<IsContextLocalAttribute> ();

			//define 
			var setting = new FieldActionSettingObject ();
			setting.key = enumField.Name;
			setting.showName = shared.prefix + actionAttr.ShowName;
			setting.contextFlags = flagsAttr?.Flags ?? shared.flags;
			setting.paramSettings = (from i in Enumerable.Range (0, actionAttr.ParamCount)
									 let paramAttr = paramAttrLookup[i].FirstOrDefault ()
									 select new FieldParamSetting (paramAttr?.ShowName, paramAttr?.Type ?? typeof (double))).ToList ();

			setting.isContextLocal = contextLocalAttr?.IsContextLocal ?? shared.contextLocal;
			setting.debugMode = debugAttr?.DebugMode ?? shared.debugMode;

			DefineAction (setting);
		}
		#endregion


		#region DrawData
		public class DrawData
		{
			public string[] keys;
			public string[] shownames;
		}

		static Dictionary<int, DrawData> variableDrawDataDict = new Dictionary<int, DrawData> ();
		static Dictionary<int, DrawData> valueDrawDataDict = new Dictionary<int, DrawData> ();
		static Dictionary<int, DrawData> eventDrawDataDict = new Dictionary<int, DrawData> ();
		static Dictionary<int, DrawData> actionDrawDataDict = new Dictionary<int, DrawData> ();


		static bool ValidateVariable (DataFieldSetting field)
		{
			var c = field.Composition;
			if (c == FieldComposition.None || c == FieldComposition.Simple)
			{
				return field.ComponentType == FieldComponents.Result;
			}
			return true;
		}

		static string GetVariableShowName (DataFieldSetting field)
		{
			var c = field.Composition;
			if (c == FieldComposition.None || c == FieldComposition.Simple)
			{
				if (field.ComponentType == FieldComponents.Result)
				{
					return field.ShowName;
				}
			}
			return field.ShowName_Full;
		}

		public static DrawData GetDrawData_Variable (int flags)
		{
			if (!variableDrawDataDict.TryGetValue (flags, out var data))
			{
				data = new DrawData ();
				data.keys = (from field in DataFields.Values
							 where (flags & field.ContextFlags) == field.ContextFlags
							 where ValidateVariable (field)
							 select field.Key).ToArray ();
				data.shownames = (from key in data.keys
								  let field = GetField (key)
								  select GetVariableShowName (field)).ToArray ();

				variableDrawDataDict[flags] = data;
			}
			return data;
		}

		public static DrawData GetDrawData_Value (int flags)
		{
			if (!valueDrawDataDict.TryGetValue (flags, out var data))
			{
				data = new DrawData ();
				data.keys = (from field in DataFields.Values
							 where (flags & field.ContextFlags) == field.ContextFlags
							 where field.Editable
							 select field.Key).ToArray ();
				data.shownames = (from key in data.keys
								  let field = GetField (key)
								  select field.ShowName_Full).ToArray ();

				valueDrawDataDict[flags] = data;
			}
			return data;
		}

		public static DrawData GetDrawData_Event (int flags)
		{
			if (!eventDrawDataDict.TryGetValue (flags, out var data))
			{
				data = new DrawData ();
				data.keys = (from e in Events.Values
							 where (flags & e.ContextFlags) == e.ContextFlags
							 select e.Key).ToArray ();
				data.shownames = (from key in data.keys
								  select GetEvent (key).ShowName).ToArray ();

				eventDrawDataDict[flags] = data;
			}
			return data;
		}

		public static DrawData GetDrawData_Action (int flags)
		{
			if (!actionDrawDataDict.TryGetValue (flags, out var data))
			{
				data = new DrawData ();
				data.keys = (from a in Actions.Values
							 where (flags & a.ContextFlags) == a.ContextFlags
							 select a.Key).ToArray ();
				data.shownames = (from key in data.keys
								  select GetAction (key).ShowName).ToArray ();

				actionDrawDataDict[flags] = data;
			}
			return data;
		}




		#endregion
	}
}
